// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/fs.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/utsname.h>
#include <linux/vmalloc.h>
#include <linux/kernel_read_file.h>

#include "dim_core.h"
#include "dim_core_policy.h"
#include "../common/dim_rb_tree.h"
#include "../common/dim_utils.h"
#include "dim_core_sig.h"

#define DIM_POLICY_ACTION "action="
#define DIM_ACTION_STRING_MAX 64

static dim_rb_handle dim_policy_list;

static const char *dim_policy_action_str[] = {
	[ACTION_LOG] = "log",
	[ACTION_KILL] = "kill",
	[POLICY_ACTION_LAST] = "",
};

struct dim_policy {
	char *path;
	enum dim_core_policy_action action;
};

static int dim_policy_compare(struct dim_policy *data1,
			      struct dim_policy *data2)
{
	if (data1 == NULL || data2 == NULL)
		return -1;

	return strcmp(data1->path, data2->path);
}

static enum dim_core_policy_action dim_policy_action_parse(const char *action)
{
	int i, len, len_action;
	const char *tmp = NULL;

	if (action == NULL)
		return ACTION_LOG;

	len_action = strlen(DIM_POLICY_ACTION);
	len = strlen(action);
	if (len <= len_action ||
		strncmp(action, DIM_POLICY_ACTION, len_action) != 0) {
		pr_warn("fail to policy action parsing of %s\n", action);
		return ACTION_LOG;
	}

	tmp = &action[len_action];
	len = len - len_action;
	for (i = ACTION_LOG; i < POLICY_ACTION_LAST; i++) {
		if (len == strlen(dim_policy_action_str[i]) &&
			strcmp(tmp, dim_policy_action_str[i]) == 0) {
			return i;
		}
	}

	pr_warn("fail to policy action parsing of %s\n", action);
	return ACTION_LOG;
}

static struct dim_policy *dim_policy_item_create(const char *path,
				enum dim_core_policy_action action)
{
	struct dim_policy *policy = NULL;
	char *path_buf = NULL;
	const char *r_path = NULL;
	unsigned int len;

	policy = kmalloc(sizeof(struct dim_policy), GFP_KERNEL);
	if (policy == NULL)
		return NULL;
	policy->action = action;

	if (path[0] == '/') {
		path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
		if (path_buf  == NULL) {
			kfree(policy);
			return NULL;
		}

		r_path = dim_get_absolute_path(path, path_buf, PATH_MAX);
		if (IS_ERR(r_path)) {
			pr_warn("fail to get absolute path of %s in policy: %ld\n",
				path, PTR_ERR(r_path));
			r_path = path;
		}
	} else {
		r_path = path;
	}

	len = strlen(r_path);
	policy->path = kmalloc(len + 1, GFP_KERNEL);
	if (policy->path == NULL) {
		kfree(policy);
		policy = NULL;
	} else {
		policy->path = strcpy(policy->path, r_path);
	}

	if (path_buf != NULL)
		kfree(path_buf);

	return policy;
}

static void dim_policy_item_destroy(struct dim_policy *policy)
{
	if (policy == NULL)
		return;

	if (policy->path != NULL)
		kfree(policy->path);

	kfree(policy);
}


static int dim_policy_proc(const char *path, const char *action_str)
{
	int ret = 0;
	struct dim_policy *policy = NULL;
	struct dim_policy *r_policy = NULL;
	enum dim_core_policy_action action = ACTION_LOG;

	action = dim_policy_action_parse(action_str);

	policy = dim_policy_item_create(path, action);
	if (policy == NULL) {
		pr_err("fail to alloc memory for policy parsing\n");
		return -ENOMEM;
	}

	ret = dim_rb_insert_data(dim_policy_list, policy, (void **)&r_policy);
	if (ret != 0)
		dim_policy_item_destroy(policy);

	return ret;
}

enum {
	PARSE_START,
	PARSE_PATH,
	PARSE_ACTION,
	PARSE_INVALID,
};

static int dim_policy_read(char *buf, loff_t buf_len)
{
	char c;
	loff_t i;
	char *path_start = NULL;
	char *action_start = NULL;
	int ret, parse_flag;
	int path_len = 0;
	int action_len = 0;
	int line_no = 0;
	int num = 0;

	parse_flag = PARSE_START;
	for (i = 0; i < buf_len; i++) {
		c = buf[i];
		switch (parse_flag) {
		case PARSE_START: /* Parse a new line */
			if (num >= DIM_MAX_POLICY_NUMBER) {
				pr_warn("more than %d policy items will be ignored\n",
					DIM_MAX_POLICY_NUMBER);
				return 0;
			}

			if (c == '\n') {
				line_no++;
				break;
			}

			if (c == '#') {
				parse_flag = PARSE_INVALID;
				break;
			}

			path_len = 1;
			path_start = &buf[i];
			action_len = 0;
			action_start = NULL;
			parse_flag = PARSE_PATH;
			break;

		case PARSE_INVALID:
			if (c == '\n') {
				line_no++;
				parse_flag = PARSE_START;
			}
			break;

		case PARSE_PATH:
			if (c == ' ' && i < buf_len - 1) {
				buf[i] = '\0';
				action_len = 1;
				action_start = &buf[i + 1];
				parse_flag = PARSE_ACTION;
			} else if (c == '\n' || i == buf_len - 1) {
				buf[i] = '\0';
				line_no++;
				ret = dim_policy_proc(path_start, action_start);
				if (ret == 0) {
					num++;
				} else {
					pr_err("fail to policy parse line %d\n",
					line_no);
				}

				parse_flag = PARSE_START;
			} else if (++path_len >= PATH_MAX) {
				pr_warn("overlength at line %d in policy\n",
					line_no + 1);
				parse_flag = PARSE_INVALID;
			}
			break;

		case PARSE_ACTION:
			if (c == '\n' || i == buf_len - 1) {
				buf[i] = '\0';
				line_no++;
				ret = dim_policy_proc(path_start, action_start);
				if (ret == 0) {
					num++;
				} else {
					pr_err("fail to policy parse line %d\n",
					line_no);
				}
				parse_flag = PARSE_START;
				break;
			}

			if (++action_len >= DIM_ACTION_STRING_MAX) {
				pr_warn("action overlength at line %d in policy\n",
					line_no + 1);
				parse_flag = PARSE_INVALID;
			}
			break;

		default:
			break;
		}
	}

	return 0;
}

int dim_policy_load(void)
{
	int ret;
	void *buf = NULL;
	void *sbuf = NULL;
	void *buf_copy = NULL;
	loff_t buf_len = 0;
	loff_t sbuf_len = 0;

	if (dim_policy_list != NULL)
		dim_policy_destroy();

	dim_policy_list = dim_rb_create((dim_rb_compare)dim_policy_compare);
	if (dim_policy_list == NULL)
		return -ENOMEM;

	ret = kernel_read_file_from_path(DIM_POLICY_PATH, 0, &buf,
		MAX_POLICY_FILE_SZIE, NULL, READING_UNKNOWN);
	if (ret < 0) {
		pr_warn("fail to read policy file: %d\n", ret);
		goto out;
	}

	buf_len = ret;

	ret = kernel_read_file_from_path(DIM_POLICY_SIG_PATH, 0, &sbuf,
		DIM_MAX_CERT_FILE_SIZE, NULL, READING_UNKNOWN);
	if (ret < 0) {
		pr_warn("fail to read policy signature file: %d\n", ret);
		goto out;
	}

	sbuf_len = ret;

	ret = dim_sig_verify(buf, buf_len, sbuf, sbuf_len);
	if (ret != 0) {
		pr_warn("fail to verify policy signature: %d\n", ret);
		goto out;
	}

	/* append one byte for policy parsing */
	buf_copy = vmalloc(buf_len + 1);
	if (buf_copy == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	memcpy(buf_copy, buf, buf_len);
	ret = dim_policy_read(buf_copy, buf_len + 1);

out:
	if (buf != NULL)
		vfree(buf);
	if (sbuf != NULL)
		vfree(sbuf);
	if (buf_copy != NULL)
		vfree(buf_copy);
	if (ret < 0)
		dim_policy_destroy();
	return ret;
}

void dim_policy_destroy(void)
{
	struct dim_policy *policy = NULL;

	while (1) {
		policy = (struct dim_policy *)
			dim_rb_remove_first_data(dim_policy_list);
		if (policy == NULL)
			break;

		dim_policy_item_destroy(policy);
	}

	dim_rb_destroy(dim_policy_list);
	dim_policy_list = NULL;
}

bool dim_policy_has_item(char *path)
{
	struct dim_policy policy = {.path = path};

	if (path == NULL || dim_policy_list == NULL)
		return false;

	/* measure kernel by default */
	if (strcmp(path, init_uts_ns.name.release) == 0)
		return true;

	/* check ko express */
	if (strncmp(path, init_uts_ns.name.release,
		strlen(init_uts_ns.name.release)) == 0)
		policy.path += (strlen(init_uts_ns.name.release) + 1);

	return dim_rb_find_data(dim_policy_list, &policy) == NULL ?
		false : true;
}

enum dim_core_policy_action dim_policy_get_action(const char *path)
{
	struct dim_policy policy = {.path = (char *)path};
	struct dim_policy *item = NULL;
	enum dim_core_policy_action action = ACTION_LOG;

	if (path == NULL || dim_policy_list == NULL)
		return action;

	item = dim_rb_find_data(dim_policy_list, &policy);
	if (item != NULL)
		action = item->action;

	return action;
}
